﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.ServiceModel;
using System.Collections;
using System.Collections.ObjectModel;
namespace MyBasicLib.Utilities
{
    public static class DynamicServiceRegister
    {
        public static void Install(Assembly assembly)
        {
            Register(assembly, null, null);

        }
        public static void Install(Assembly assembly, Type factory)
        {
            try
            {
                Register(assembly, factory, null);
            }
            catch (Exception ex)
            {
                IOC.DefaultContainer.Resolve<ILogger>().Error(ex.ToString());
                throw;
            }
        }
        public static void Register(Assembly assembly, Type factory, String basePath)
        {
            basePath = basePath ?? "~/";
            if (!basePath.EndsWith("/"))
                basePath += "/";
            // Invoke the "EnsureInitialized" method so we can register our service right now.
            // I know, this is a risky move, but is simpler than other solutions.
            // Seems to work so this method doesn't have dependencies on other stuff.
            // Other solutions might be: 
            //      *- Create a "Startup" normal WCF service and hook the OnStart method to do this.
            //          This would work only under AppFabric where AutoStart is enabled for all services.
            //          (and I have to test it, is just an idea)
            //      *- set a timer here to try to initialize this in some way, hoping that the ServiceHostingEnvironment
            //          class gets initialized somehow, even is nobody have made an Http request.
            try
            {
                DynamicServiceHelper.EnsureInitialized();
                // Now, we register our service
                var types = assembly.GetTypes();//.Where(t => t.Name.EndsWith("Service"));

                foreach (var type in types)
                {
                    if (type.GetCustomAttributes<ServiceContractAttribute>(true).Length > 0)
                        DynamicServiceHelper.RegisterService(basePath + type.Name + ".svc", factory, type);
                }
            }
            catch (Exception ex)
            {
                IOC.DefaultContainer.Resolve<ILogger>().Error(ex.ToString());
            }
        }
        static class DynamicServiceHelper
        {
            static object _syncRoot = new object();
            static FieldInfo hostingManagerField;
            static MethodInfo ensureInitialized;
            static FieldInfo serviceActivationsField;

            static DynamicServiceHelper()
            {
                ensureInitialized = typeof(ServiceHostingEnvironment).GetMethod("EnsureInitialized", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod);
                hostingManagerField = typeof(ServiceHostingEnvironment).GetField("hostingManager", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetField);
            }

            public static void EnsureInitialized()
            {
                ensureInitialized.Invoke(null, new object[] { });
            }

            public static void RegisterService(string addr, Type factory, Type service)
            {

                // This is the code that injects the service activation configuration.
                // In WCF 4 we have the "not very well known" CBA Services (Configuration Based Activation)
                // to allow us to define "file-less" WCF Services (like our Service2 here in this demo) where
                // we have a section in our web.config to register services without the need of having a physical .svc file.
                // The section in the web.config looks like this:
                //
                //      <serviceHostingEnvironment multipleSiteBindingsEnabled="true" >
                //          <serviceActivations>
                //              <add relativeAddress="Service2.svc" service="WcfService2.Service2" />
                //          </serviceActivations>
                //      </serviceHostingEnvironment>
                //
                // And is this configuration what we are going to inject at runtime to simulate having that 
                // information in our web.config, while we haven't.

                lock (_syncRoot)
                {
                    object hostingManager = hostingManagerField.GetValue(null);

                    if (serviceActivationsField == null)
                    {
                        serviceActivationsField = hostingManager.GetType().GetField("serviceActivations", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField);
                    }

                    Hashtable serviceActivations = (Hashtable)serviceActivationsField.GetValue(hostingManager);

                    string value = string.Format("{0}|{1}|{2}", addr, factory == null ? String.Empty : factory.AssemblyQualifiedName, service.AssemblyQualifiedName);

                    if (!serviceActivations.ContainsKey(addr))
                    {
                        serviceActivations.Add(addr, value);
                    }
                }
            }
        }


        /// <summary>Searches and returns attributes. The inheritance chain is not used to find the attributes.</summary>
        /// <typeparam name="T">The type of attribute to search for.</typeparam>
        /// <param name="type">The type which is searched for the attributes.</param>
        /// <returns>Returns all attributes.</returns>
        static T[] GetCustomAttributes<T>(this Type type) where T : Attribute
        {
            return GetCustomAttributes(type, typeof(T), false).Select(arg => (T)arg).ToArray();
        }

        /// <summary>Searches and returns attributes.</summary>
        /// <typeparam name="T">The type of attribute to search for.</typeparam>
        /// <param name="type">The type which is searched for the attributes.</param>
        /// <param name="inherit">Specifies whether to search this member's inheritance chain to find the attributes. Interfaces will be searched, too.</param>
        /// <returns>Returns all attributes.</returns>
        static T[] GetCustomAttributes<T>(this Type type, bool inherit) where T : Attribute
        {
            return GetCustomAttributes(type, typeof(T), inherit).Select(arg => (T)arg).ToArray();
        }

        /// <summary>Private helper for searching attributes.</summary>
        /// <param name="type">The type which is searched for the attribute.</param>
        /// <param name="attributeType">The type of attribute to search for.</param>
        /// <param name="inherit">Specifies whether to search this member's inheritance chain to find the attribute. Interfaces will be searched, too.</param>
        /// <returns>An array that contains all the custom attributes, or an array with zero elements if no attributes are defined.</returns>
        private static object[] GetCustomAttributes(Type type, Type attributeType, bool inherit)
        {
            if (!inherit)
            {
                return type.GetCustomAttributes(attributeType, false);
            }

            var attributeCollection = new Collection<object>();
            var baseType = type;

            do
            {
                baseType.GetCustomAttributes(attributeType, true).Apply(attributeCollection.Add);
                baseType = baseType.BaseType;
            }
            while (baseType != null);

            foreach (var interfaceType in type.GetInterfaces())
            {
                GetCustomAttributes(interfaceType, attributeType, true).Apply(attributeCollection.Add);
            }

            var attributeArray = new object[attributeCollection.Count];
            attributeCollection.CopyTo(attributeArray, 0);
            return attributeArray;
        }

        /// <summary>Applies a function to every element of the list.</summary>
        private static void Apply<T>(this IEnumerable<T> enumerable, Action<T> function)
        {
            foreach (var item in enumerable)
            {
                function.Invoke(item);
            }
        }
    }
}